#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# @Author  : Leo.W
# @Date    : 2024/1/23 10:08
# @File    : 2-decimal.py
# @Software: PyCharm

"""
* decimal: 十进制定点和浮点运算
decimal 模块提供了对快速且正确舍入的十进制浮点运算的支持。
float 为二进制浮点值，仅表示期望结果的十进制近似值而非准确值。
decimal 模块旨在支持“无偏差，精确无舍入的十进制算术（有时称为定点数算术）和有舍入的浮点数算术”。
例如：
在 float 类型下 1.1 + 2.2被显示为 3.3000000000000003 ,结果只是期望结果的近似值而非准确值。而 decimal 类型可以精确的表示为 3.30
decimal 模块包含有效位的概念因而使得 1.30 + 1.20 等于 2.50。 末尾的零会被保留以表明有效位。 这是货币相关应用的惯例表示方式。
对于乘法，则按“教科书”方式来使用被乘数中的所有数位。 例如，1.3 * 1.2 结果为 1.56 而 1.30 * 1.20 结果为 1.5600。

* decimal 数值属于不可变对象：
它由一个符号、一个系数值及一个指数值组成。 为了保留有效位，系数值不会截去末尾的零。
decimal 数值还包括特殊值如 Infinity, -Infinity 和 NaN。 该标准还会区分 -0 和 +0。

* 使用 decimal 的方式是先导入该模块：from decimal import *
"""
from decimal import *

"""
Decimal 上下文对象：可使用 getcontext() 和 setcontext(c) 函数来读取或修改：
注意：
getcontext() 函数会为每个线程访问不同的 Context 对象。 具有单独线程上下文意味着线程可以修改上下文 (例如 getcontext().prec=10) 而不影响其他线程。
同理 setcontext() 会为当前上下文的目标自动赋值。
如果在调用 setcontext() 之前调用了 getcontext()，则 getcontext() 将自动创建一个新的上下文在当前线程中使用。
由于新的上下文拷贝自一个名为 DefaultContext 的原型上下文。 因此要控制默认值以便每个线程在应用运行期间都使用相同的值，可以直接修改 DefaultContext 对象。
"""
DefaultContext.prec = 12
DefaultContext.rounding = ROUND_DOWN
DefaultContext.traps = ExtendedContext.traps.copy()
DefaultContext.traps[InvalidOperation] = 1
setcontext(DefaultContext)  # 为所有线程设置默认的context
# 应当在任何线程启动 之前 完成上述设置，使得调用 getcontext() 的线程之间不会产生竞争条件
# Afterwards, the threads can be started,for example:
# thread1.start()
# thread2.start()
# thread3.start()


# 新的上下文也可使用下述的 Context 构造器来创建。
def decimal_context():
    """
    使用 Context 构造器创建新的上下文:
     class decimal.Context(prec=None, rounding=None, Emin=None, Emax=None, capitals=None, clamp=None, flags=None, traps=None)

    模块还提供了三种预设的上下文:
        * class decimal.BasicContext：精度设为九。 舍入设为 ROUND_HALF_UP。清除所有旗标。 启用所有陷阱（视为异常），但 Inexact, Rounded 和 Subnormal 除外。
        * class decimal.ExtendedContext：精度设为九。 舍入设为 ROUND_HALF_UP。 清除所有旗标。 不启用任何陷阱（因此在计算期间不会引发异常）。
        * class decimal.DefaultContext：被 Context 构造器用作新上下文的原型：创建一个新上下文，如果某个字段未指定或为 None，则从 DefaultContext 拷贝默认值。
    默认值如下：
    c = Context(
       prec=28,                    # 用于设置该上下文中算术运算的精度的 [1, MAX_PREC] 范围内的整数。
       rounding=ROUND_HALF_EVEN,   # 舍入模式
       Emin=-999999,               # Emin 和 Emax 字段是指定指数所允许的外部上限的整数
       Emax=999999,
       capitals=1,                 # 默认值1-表示指数将附带大写的 E 打印出来。0-表示指数使用小写的 e,如：Decimal('6.02e+23')
       clamp=0,                    # 如果设为 1，则 Decimal 实例的指数 e 的表示范围在此上下文中将严格限制在 Emin - prec + 1 <= e <= Emax - prec + 1 范围内
       flags=[],                   # traps 和 flags 字段列出要设置的任何信号。 通常，新上下文应当只设置 traps 而让 flags 为空。
       traps=[Overflow, DivisionByZero,InvalidOperation]
    )

    注：
    traps 和 flags 信号->如果为信号设定了上下文的陷阱启用器，则条件会导致特定的 Python 异常被引发。
    举例来说，如果设定了 DivisionByZero 陷阱，则当遇到此条件时就将引发 DivisionByZero 异常。
    :return: context
    """
    # 通过 decimal.getcontext() 查看当前上下文，并在必要时为精度、舍入或启用的陷阱设置新值:
    context = getcontext()  # 仅作用于当前线程
    # 注意：context 精度不会影响存储的位数。 例如，Decimal('3.00000') 记录所有五个零，即使上下文精度只有三。
    context.prec = 6  # 设置精度为 6 位小数,上下文精度和舍入仅在算术运算期间发挥作用
    context.rounding = ROUND_UP  # 设置四舍五入规则
    return context


# 基于 class decimal.Decimal(value='0', context=None) 创建一个 Decimal 对象
def decimal_constructor():
    """
    创建 Decimal 对象： Decimal 实例可以基于整数、字符串、浮点数或元组来构建。
    基于整数或浮点数进行构建将执行该整数或浮点数值的精确转换。
    Decimal 数字包括特殊值如代表“非数字”的 NaN，正的和负的 Infinity 以及 -0:
    :return: None
    """
    # 如果 value 参数是一个字符串，会删除掉前导和尾随空格字符以及下划线后转换成符合十进制的数字
    print(f'Decimal(" 3.1415926_") => {Decimal(" 3.1415926_")}')
    print(f"Decimal(10) => {Decimal(10)}")

    f = 3.14  # float 类型
    # 如果 value 参数是 float ，则二进制浮点值无损地转换为其精确的十进制等效值。 此转换通常需要53位或更多位数的精度。
    # Decimal(3.14) => 3.140000000000000124344978758017532527446746826171875 由此可以看出 Decimal 比 float 精度跟更高
    print(f"float => {f} , Decimal(3.14) => {Decimal(f)}")

    # 如果 value 参数是一个 tuple，它应当有三个组成部分，一个符号 (0 表示正数 1 表示负数)，一个由数字组成的 tuple，以及一个整数指数值。
    # (0, (1, 4, 1, 4), -3)) => 0 表示正数，(1, 4, 1, 4) 表示数字 1414，-3 表示 保留3为小数 ==> 1.1414
    print(f"Decimal((0, (1, 4, 1, 4), -3)) => {Decimal((0, (1, 4, 1, 4), -3))}")

    # Decimal 数字包括特殊值如代表“非数字”的 NaN，正的和负的 Infinity 以及 -0:
    print(
        f"Decimal('NaN') => {Decimal('NaN')} , Decimal('Infinity') => {Decimal('Infinity')}, Decimal('-0') => {Decimal('-0')}"
    )  # Decimal('NaN') => NaN , Decimal('Infinity') => Infinity, Decimal('-0') => -0


# decimal_constructor()


def using_decimal():
    """
    使用 decimal 的注意事项：
    :return:
    """
    # 当余数运算符 % 应用于Decimal对象时，结果的符号是【被除数】的符号，而不是【除数】的符号:
    # (-7) % 4=1 , Decimal(-7) % Decimal(4) = -3
    print(
        f"(-7) % 4={(-7) % 4} , Decimal(-7) % Decimal(4) = {Decimal(-7) % Decimal(4)}"
    )
    # 整数除法运算符 // 的行为类似，返回真商的整数部分（截断为零）而不是它的向下取整，以便保留通常的标识：
    # (-7) // 4=-2 , Decimal(-7) // Decimal(4) = -1
    print(
        f"(-7) // 4={(-7) // 4} , Decimal(-7) // Decimal(4) = {Decimal(-7) // Decimal(4)}"
    )

    # ---------常用方法--------- #
    # compare(other, context=None)：比较两个 Decimal 实例的值。 compare() 返回一个 Decimal 实例
    # a or b is a NaN  ==> Decimal('NaN')
    # a < b            ==> Decimal('-1')
    # a == b           ==> Decimal('0')
    # a > b            ==> Decimal('1')
    a, b, c = Decimal(10), Decimal(10.001), Decimal("NaN")
    print(a.compare(b), a.compare(c), b.compare(a))  # -1 NaN 1

    # compare_total(other, context=None): 与compare类似，两个 Decimal 实例具有相同的数值但不同的表示形式则不相等
    print(Decimal(10.00).compare_total(Decimal(10)))  # 0
    print(Decimal("10").compare_total(Decimal("10.00")))  # 1

    # quantize(exp, rounding=None, context=None)：返回的值等于exp舍入后的数值，并且具rounding的指数
    print(Decimal("1.41421356").quantize(Decimal("1.000")))  # 1.414
    print(Decimal("2.0002").quantize(Decimal("2.000")))  # 2.000


using_decimal()
